﻿/**
 * 
 * Copyright 2009 Mike Jamer [email: mikejamer@gmail.com ]
 * 
 * This File is part of Moose IRC
 * 
 * MooseIRC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * MooseIRC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MooseIRC.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace mooseirc.irc {
    
    /// <summary>
    /// Manages Communication to the IrcServer
    /// </summary>
    [CLSCompliant(true)]
    public sealed class IrcConnection : IDisposable {

        /// <summary>
        /// Client Socket
        /// </summary>
        private Socket _socket;

        /// <summary>
        /// Initialize IrcConnection
        /// </summary>
        internal IrcConnection() 
            : base()
        {
            return;
        }

        /// <summary>
        /// Signal Indicating a response from the server
        /// </summary>
        public event ResponseHandler Response;

        /// <summary>
        /// Connect socket to the IRC server
        /// </summary>
        /// <param name="host">host address of server</param>
        /// <param name="port">port number to connect to</param>
        /// <returns>true if connection was successful</returns>
        public bool connect(string host, int port)
        {
            if (_socket != null)
                disconnect();
            _socket = utility.NetworkUtility.createTcpSocket();
            _socket.Connect(utility.NetworkUtility.resolve(host), port);
            if (_socket.Connected)
            { //TODO: hook up recieve
                recieve();
            }
            return _socket.Connected;
        }

        /// <summary>
        /// Disconnect from the server, point socket to null
        /// </summary>
        /// <returns>true if disconnect was successful</returns>
        public bool disconnect()
        {
            if (_socket != null)
            {
                _socket.Disconnect(false);
                _socket = null;
            }
            return _socket == null;
        }

        /// <summary>
        /// Send data to the server
        /// </summary>
        /// <param name="command">command to send to the server</param>
        public void send(string command)
        {
            byte[] sendBuffer = Encoding.UTF8.GetBytes(command + "\r\n"); //Appending the \r\n so you dont have to
            _socket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, onSendComplete, sendBuffer);
            return;
        }

        /// <summary>
        /// Recieve data from the server
        /// </summary>
        private void recieve()
        {

            //TODO: Address an issue, where this will throw an exception when we disconnect

            byte[] recieveBuffer = new byte[512];

            try {
                _socket.BeginReceive(recieveBuffer, 0, recieveBuffer.Length, SocketFlags.None, onRecvComplete, recieveBuffer);
            } catch (Exception) {
                //TODO: indicate something has gone terribly wrong.
                //I know we get an exception here if we disconnect.  But this is nothing but a scratch pad for thoughts, so Im not bothering right now...
            }
            return;
        }

        /// <summary>
        /// Async Send Complete, this doesnt do much.
        /// </summary>
        /// <param name="result">Async Results</param>
        private void onSendComplete(IAsyncResult result)
        {
            byte[] sendBuffer = (byte[])result.AsyncState;
            return;
        }

        /// <summary>
        /// Async Recieve Complete,
        /// This is a major driving method in this system.
        /// </summary>
        /// <param name="result">Async Result</param>
        private void onRecvComplete(IAsyncResult result)
        {
            byte[] recieveBuffer = (byte[])result.AsyncState;
            int recv = _socket.EndReceive(result);

            if (recv == 0) return;
            
            //encode input, and break it down into lines
            foreach (string line in processInput(recieveBuffer, recv))
                Response(line); //Notify Client of response

            recieve(); //Set up recieve again
            return;
        }

        /// <summary>
        /// Breaks input down into multiple inputs.  Inputs are separated via CRLF breaks
        /// </summary>
        /// <param name="input">Data that has in</param>
        /// <param name="length">length of data recieved, as reported by the socket recieve method</param>
        /// <returns>an array of 'lines' to parse </returns>
        private string[] processInput(byte[] input, int length)
        {
            return Encoding.UTF8.GetString(input, 0, length).Split(
                new string[] { "\r\n" },
                StringSplitOptions.RemoveEmptyEntries);
        }

        /// <summary>
        /// Internal Trigger Handler
        /// </summary>
        /// <param name="response">response from server</param>
        /// <remarks>
        /// This is primarily here, simply to avoid a null reference exception, in the unlikely event that 
        /// that this event is not being subscribed to.
        /// </remarks>
        private void onResponse(string response)
        {
            return;
        }


        #region IDisposable Members

        /// <summary>
        /// Disposes Connection
        /// </summary>
        public void Dispose()
        {
            disconnect();
            return;
        }

        #endregion
    }
}
